/*******************************************************************************
 *     SDR Trunk 
 *     Copyright (C) 2014 Dennis Sheirer
 * 
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>
 ******************************************************************************/
package edac;

import java.util.BitSet;

import bits.BinaryMessage;

/**
 * Passport CRC checksum utility
 * 
 * Passport message blocks are 68 bits in length as follows:
 *    0 -  8: Sync
 * 	  9 - 59: message bits
 *   60 - 67: CRC-7 check bits plus 1 parity
 *        
 * Passport uses a CRC-7 (0x44) with an initial fill of 0x44 plus parity set.
 * 
 * CRC-7 Generating Polynomial: x6 + x2 + 1 (0x44)
 */
public class CRCPassport
{
	private static final byte sFILL_00 = (byte)0x00;
	
	private static byte[] sCHECKSUMS = new byte[]
	{	
		(byte)0x6E, //DCC 1
		(byte)0xBF, //DCC 0
		(byte)0xD6, //LCN 10
		(byte)0xE3, //LCN 9
		(byte)0xF8, //LCN 8
		(byte)0x7C, //LCN 7
		(byte)0x3E, //LCN 6
		(byte)0x97, //LCN 5
		(byte)0xC2, //LCN 4
		(byte)0xE9, //LCN 3
		(byte)0x75, //LCN 2
		(byte)0x3B, //LCN 1
		(byte)0x94, //LCN 0
		(byte)0x4A, //SITE 6
		(byte)0xAD, //SITE 5
		(byte)0x57, //SITE 4
		(byte)0xA2, //SITE 3
		(byte)0xD9, //SITE 2
		(byte)0x6D, //SITE 1
		(byte)0x37, //SITE 0
		(byte)0x92, //GROUP 15
		(byte)0xC1, //GROUP 14
		(byte)0x61, //GROUP 13
		(byte)0x31, //GROUP 12
		(byte)0x19, //GROUP 11
		(byte)0x0D, //GROUP 10
		(byte)0x07, //GROUP 9
		(byte)0x8A, //GROUP 8
		(byte)0xCD, //GROUP 7
		(byte)0x67, //GROUP 6
		(byte)0xBA, //GROUP 5
		(byte)0xD5, //GROUP 4
		(byte)0x6B, //GROUP 3
		(byte)0xBC, //GROUP 2
		(byte)0x5E, //GROUP 1
		(byte)0xA7, //GROUP 0
		(byte)0xDA, //TYPE 3
		(byte)0xE5, //TYPE 2
		(byte)0x73, //TYPE 1
		(byte)0xB0, //TYPE 0
		(byte)0x58, //FREE 10
		(byte)0x2C, //FREE 9
		(byte)0x16, //FREE 8
		(byte)0x83, //FREE 7
		(byte)0xC8, //FREE 6
		(byte)0x64, //FREE 5
		(byte)0x32, //FREE 4
		(byte)0x91, //FREE 3
		(byte)0x49, //FREE 2
		(byte)0x25, //FREE 1
		(byte)0x13 //FREE 0
	};

	/**
	 * Determines if message bits 9 - 59 pass the CRC checksum 
	 * contained in bits 60 - 68, using a lookup table of CRC checksum values
	 * derived from the CRC-7 value and the final parity bit
	 */
	public static CRC check( BitSet msg )
	{
		CRC crc = CRC.UNKNOWN;
		
		byte calculated = 0x0; //Starting value for an OSW

        //Iterate bits that are set and XOR running checksum with lookup value
        for (int i = msg.nextSetBit( 9 ); i >= 9 && i <= 59; i = msg.nextSetBit( i+1 ) ) 
		{
			calculated ^= sCHECKSUMS[ i - 9 ];
		}

		//Apply the message checksum to derive the residual
		calculated ^= getChecksum( msg );
		
		switch( (byte)calculated )
		{
			case sFILL_00:
	            crc = CRC.PASSED;
				break;
			default:
				crc = CRC.FAILED_CRC;
		}
		
		return crc;
	}
	
    public static byte getResidual( BitSet msg )
    {
        byte calculated = 0x0; //Initial fill of zero

        //Iterate bits that are set and XOR running checksum with lookup value
        for (int i = msg.nextSetBit( 9 ); i >= 9 && i <= 59; i = msg.nextSetBit( i+1 ) ) 
        {
            calculated ^= sCHECKSUMS[ i - 9 ];
        }
        
        calculated ^= getChecksum( msg );
        
        return calculated;
    }
    
	public static byte[] getChecks()
	{
		return sCHECKSUMS;
	}

	/**
	 * Returns the integer value of the 7 bit crc checksum
	 */
    public static byte getChecksum( BitSet msg )
    {
        byte retVal = 0x0;
    	
    	for( int x = 0; x < 8; x++ )
    	{
    		if( msg.get( x + 60 ) )
    		{
    			retVal ^= (byte)( 1<<( 7 - x ) );
    		}
    	}
    	
    	return retVal;
    }

    /**
     * Initial take on error correction.  The majority of the errors detected
     * are in the final 2 bits, owing to the forward looking soft bit detection
     * used in the decoder.  For right now, correct any messages where the 
     * residual indicates that the final 1 or 2 bits are bad.
     */
    public static BinaryMessage correct( BinaryMessage msg )
    {
    	int residual = (int)( 0xFF & getResidual( msg ) );
    	
    	switch( residual )
    	{
    		case 1:
    		case 136:
    			msg.flip( 67 );
    			break;
    		case 3:
    		case 138:
    			msg.flip( 66 );
    			msg.flip( 67 );
    			break;
    		case 2:
    		case 139:
    			msg.flip( 66 );
    			break;
    		case 140:
    			msg.flip( 65 );
    			msg.flip(  67 );
    			break;
    		case 153:
    			msg.flip(  67 );
    			break;
    		case 142:
    			msg.flip( 65 );
    			msg.flip( 66 );
    			msg.flip( 67 );
    			break;
    	}

		return msg;
    }
    
    /**
     * Performs an even parity check on the bitset.  If the number of bits that
     * are set is an even number, then it passes the even parity check
     */
    public static boolean isEvenParity( BinaryMessage bits )
    {
    	return ( bits.cardinality() % 2 == 0 );
    }
}
